/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ArkFile, ts, AstTreeUtils } from "arkanalyzer";
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { BaseChecker, BaseMetaData } from "../BaseChecker";
import { FileMatcher, MatcherCallback, MatcherTypes } from '../../Index';
import { Defects, IssueReport } from "../../model/Defects";
import { Rule } from "../../model/Rule";
import { RuleListUtil } from "../../utils/common/DefectsList";
import { RuleFix } from '../../model/Fix';

const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'NoUnnecessaryTypeAssertionCheck');
const gMetaData: BaseMetaData = {
    severity: 2,
    ruleDocPath: "docs/no-unnecessary-type-assertion-check.md",
    description: "Disallow type assertions that do not change the type of an expression.",
};

interface Options {
    typesToIgnore?: string[]
}

// 定义一个接口，用于存储问题的行列信息
interface LocationInfo {
    fileName: string;
    line: number;
    startCol: number;
    endCol: number;
    start: number;
    end: number;
    assertionName: string;
}

export class NoUnnecessaryTypeAssertionCheck implements BaseChecker {

    readonly metaData: BaseMetaData = gMetaData;
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];

    private defaultOptions: Options = {};

    private fileMatcher: FileMatcher = {
        matcherType: MatcherTypes.FILE,
    };

    public registerMatchers(): MatcherCallback[] {
        const fileMatcherCb: MatcherCallback = {
            matcher: this.fileMatcher,
            callback: this.check
        }
        return [fileMatcherCb];
    }

    public check = (arkFile: ArkFile) => {
        if (arkFile instanceof ArkFile) {
            const code = arkFile.getCode();
            if (!code) {
                return;
            }
            const filePath = arkFile.getFilePath();
            const asRoot = AstTreeUtils.getASTNode(arkFile.getName(), code);

            // 检查类型断言
            const LocationInfos = this.checkTypeAssertion(asRoot);

            // 输出结果
            LocationInfos.forEach(loc => {
                this.addIssueReportNodeFix(loc, filePath);
            });
        }
    }

    private checkTypeAssertion(sourceFile: ts.SourceFile): LocationInfo[] {
        const locationInfos: LocationInfo[] = [];
        const options: Options = this.rule && this.rule.option[0] ? this.rule.option as Options : this.defaultOptions;
        const visit = (node: ts.Node) => {
            if (ts.isNonNullExpression(node)) {
                this.handleNonNullAssertion(node, sourceFile, options, locationInfos);
            } else if (ts.isTypeAssertionExpression(node) || ts.isAsExpression(node)) {
                this.handleTypeAssertion(node, sourceFile, options, locationInfos);
            }

            ts.forEachChild(node, visit);
        };

        visit(sourceFile);
        return locationInfos;
    }

    private handleNonNullAssertion(
        node: ts.NonNullExpression,
        sourceFile: ts.SourceFile,
        options: Options,
        locationInfos: LocationInfo[]
    ) {
        const exclamationNode = node.expression;
        const isRedundant = this.isRedundantNonNullAssertion(exclamationNode, sourceFile);

        if (isRedundant) {
            this.addUnnecessaryAssertion(locationInfos, sourceFile, node, exclamationNode, false, options);
        }
    }

    private handleTypeAssertion(
        node: ts.TypeAssertion | ts.AsExpression,
        sourceFile: ts.SourceFile,
        options: Options,
        locationInfos: LocationInfo[]
    ) {
        const expression = node.expression;
        const typeNode = node.type;

        // 检查类型断言是否冗余
        const isRedundant = this.isRedundantTypeAssertion(expression, typeNode);

        if (isRedundant) {
            this.addUnnecessaryAssertion(locationInfos, sourceFile, node, expression, true, options);
        }
    }

    private isRedundantNonNullAssertion(node: ts.Node, sourceFile: ts.SourceFile): boolean {
        // 检查非空断言是否冗余
        // 假设如果表达式是一个函数参数，并且该参数的类型不包含 `null` 或 `undefined`，则认为冗余
        if (ts.isIdentifier(node)) {
            const paramName = node.getText(sourceFile);
            const parentFunction = this.findParentFunction(node);

            if (parentFunction) {
                const param = parentFunction.parameters.find(p => p.name.getText() === paramName);
                if (param) {
                    const paramType = param.type?.getText(sourceFile) || '';
                    return !paramType.includes('null') && !paramType.includes('undefined');
                }
            }
        }
        return false;
    }

    private isRedundantTypeAssertion(expression: ts.Node, typeNode: ts.TypeNode): boolean {
        // 检查类型断言是否冗余
        // 假设如果表达式的类型已经与目标类型一致，则认为冗余
        const expressionType = this.getInferredType(expression);
        const targetType = typeNode.getText();

        return expressionType === targetType;
    }

    private getInferredType(node: ts.Node): string {
        // 尝试获取表达式的推断类型
        if (ts.isIdentifier(node)) {
            const parent = node.parent;
            if (ts.isVariableDeclaration(parent)) {
                return parent.type?.getText() || 'unknown';
            } else if (ts.isParameter(parent)) {
                return parent.type?.getText() || 'unknown';
            }
        }
        return 'unknown';
    }

    private findParentFunction(node: ts.Node): ts.FunctionDeclaration | undefined {
        let current: ts.Node | undefined = node.parent;
        while (current) {
            if (ts.isFunctionDeclaration(current)) {
                return current;
            }
            current = current.parent;
        }
        return undefined;
    }

    private addUnnecessaryAssertion(locationInfos: LocationInfo[],
        sourceFile: ts.SourceFile,
        node: ts.Node,
        exclamationNode: ts.Expression,
        isAsExpression: boolean,
        options: Options
    ) {
        if (!options.typesToIgnore || !options.typesToIgnore.includes(exclamationNode.getText())) {
            const { line, character } = sourceFile.getLineAndCharacterOfPosition(exclamationNode.getStart());
            const endCharacter = character + exclamationNode.getWidth();
            if (!isAsExpression) {
                locationInfos.push({
                    fileName: sourceFile.fileName,
                    line: line + 1,
                    startCol: character + 1,
                    endCol: endCharacter + 1,
                    start: exclamationNode.getEnd(),
                    end: exclamationNode.getEnd() + 1,
                    assertionName: exclamationNode.getText()
                });
            }
        }
    }

    // 创建修复对象 
    private ruleFix(loc: LocationInfo): RuleFix {
        return { range: [loc.start, loc.end], text: '' };
    }

    private addIssueReportNodeFix(loc: LocationInfo, filePath: string) {
        const severity = this.rule.alert ?? this.metaData.severity;
        let defect = new Defects(loc.line, loc.startCol, loc.endCol, this.metaData.description, severity,
            this.rule.ruleId, filePath, this.metaData.ruleDocPath, true, false, false);
        let fix: RuleFix = this.ruleFix(loc);
        this.issues.push(new IssueReport(defect, fix));
        RuleListUtil.push(defect);
    }
}